package graphicView;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;

import javax.swing.BorderFactory;
import javax.swing.JPanel;

import model.AnchorPoint;
import model.ClassImpl;
import model.RelationshipImpl;

import java.util.Observable;
import java.util.Observer;
import java.util.Vector;

/**
 * Team EBE
 * 
 * MyPanel is the class used to represent the view on the screen with the
 * various components being added to the UML editor
 * 
 */
public class MyPanel extends JPanel implements ActionListener, Observer {

	private Vector<ClassGraphicObject> classToDisplay;
	private Vector<RelationshipGraphicObject> relationshipToDisplay;
	private Vector<PackageGraphicObject> packageToDisplay;

	private ActiveClassNotifier activeClassNotifier;
	private Boolean relationshipIsActive;
	private RelationshipImpl theActiveRelationship;
	private int srcOrDest;
	private int mouseToOriginOffsetX, mouseToOriginOffsetY;
	private boolean newRelMode = false;
	private GraphicController theController;

	RelationshipFactory relFactory = new RelationshipFactory();

	/**
	 * MyPanel creates the panel and the various components to be added to the
	 * panel
	 * 
	 * @param classToDisplay
	 *            the class object to be displayed
	 * @param relationshipToDisplayIn
	 *            the relationship object to be displayed
	 * @param packageToDisplay
	 *            the package object to be displayed
	 * @param activeClassNotifierIn
	 *            the notifier of the active class passed in
	 * @param theControllerIn
	 *            the controller to be passed in
	 */
	public MyPanel(Vector<ClassGraphicObject> classToDisplay,
			Vector<RelationshipGraphicObject> relationshipToDisplayIn,
			Vector<PackageGraphicObject> packageToDisplay,
			ActiveClassNotifier activeClassNotifierIn,
			GraphicController theControllerIn) {
		this.packageToDisplay = packageToDisplay;
		this.classToDisplay = classToDisplay;
		this.relationshipToDisplay = relationshipToDisplayIn;
		this.setBackground(Color.white);
		this.theController = theControllerIn;

		final Vector<PackageGraphicObject> packagesForListeners = this.packageToDisplay;
		final Vector<ClassGraphicObject> classForListeners = this.classToDisplay;
		final Vector<RelationshipGraphicObject> relationshipForListeners = this.relationshipToDisplay;

		activeClassNotifier = activeClassNotifierIn;
		relationshipIsActive = false;

		setBorder(BorderFactory.createLineBorder(Color.black));

		addMouseListener(new MouseAdapter() {

			/**
			 * mousePressed is executed when the mouse is pressed on a component
			 */
			public void mousePressed(MouseEvent e) {
				if (!newRelMode) {
					boolean clickedOnAnchor = false;
					boolean clickedOnClass = false;
					RelationshipGraphicObject rgo;
					for (int i = relationshipForListeners.size() - 1; i >= 0; i--) {
						rgo = relationshipForListeners.elementAt(i);
						if ((((e.getX() - rgo.getModel().getDest().x) * (e
								.getX() - rgo.getModel().getDest().x)) + ((e
								.getY() - rgo.getModel().getDest().y) * (e
								.getY() - rgo.getModel().getDest().y))) <= 100) {

							activeClassNotifier.aRelationshipEndPointIsActive();
							theActiveRelationship = rgo.getModel();
							srcOrDest = 0;
							clickedOnAnchor = true;
							repaint();
							break;
						} else if ((((e.getX() - rgo.getModel().getSrc().x) * (e
								.getX() - rgo.getModel().getSrc().x)) + ((e
								.getY() - rgo.getModel().getSrc().y) * (e
								.getY() - rgo.getModel().getSrc().y))) <= 100) {

							activeClassNotifier.aRelationshipEndPointIsActive();
							theActiveRelationship = rgo.getModel();
							srcOrDest = 1;
							clickedOnAnchor = true;
							repaint();
							break;
						}
						if (clickedOnAnchor)
							break;

					}
					if (!clickedOnAnchor) {
						ClassGraphicObject cgo;
						for (int i = classForListeners.size() - 1; i >= 0; i--) {
							cgo = classForListeners.elementAt(i);

							if (e.getX() > cgo.getoX()
									&& e.getY() > cgo.getoY()
									&& e.getX() < cgo.getoX() + cgo.getWidth()
									&& e.getY() < cgo.getoY() + cgo.getHeight()) {
								mouseToOriginOffsetX = e.getX() - cgo.getoX();
								mouseToOriginOffsetY = e.getY() - cgo.getoY();
								System.out.println(mouseToOriginOffsetX + " "
										+ mouseToOriginOffsetY);
								cgo.activate();
								activeClassNotifier.changeClass(cgo.getModel());
								classForListeners.remove(cgo);
								classForListeners.add(cgo);
								clickedOnClass = true;
								repaint();
								invalidate();
								break;
							}
						}
						if (!clickedOnClass) {
							PackageGraphicObject pgo;
							for (int i = packagesForListeners.size() - 1; i >= 0; i--) {
								pgo = packagesForListeners.elementAt(i);
								if (e.getX() > pgo.getoX()
										&& e.getY() > pgo.getoY()
										&& e.getX() < pgo.getoX()
												+ pgo.getWidth()
										&& e.getY() < pgo.getoY()
												+ pgo.getHeight()) {
									pgo.activate();
									activeClassNotifier.changePackage(pgo.getModel());
									mouseToOriginOffsetX = e.getX()
											- pgo.getoX();
									mouseToOriginOffsetY = e.getY()
											- pgo.getoY();
									activeClassNotifier.changePackage(pgo
											.getModel());
									packagesForListeners.remove(pgo);
									packagesForListeners.add(pgo);
									repaint();
									invalidate();
									break;
								}
							}
						}
					}
				} else {
					boolean foundPoint = false;
					for (ClassGraphicObject cgo : classForListeners) {
						for (AnchorPoint ap : cgo.getAnchorPoints()) {
							if ((((e.getX() - ap.x) * (e.getX() - ap.x)) + ((e
									.getY() - ap.y) * (e.getY() - ap.y))) <= 100) {
								relFactory.setSrc(ap);
								foundPoint = true;
								System.out.println("found src");
								repaint();
								invalidate();
								break;

							}

						}
						if (foundPoint)
							break;
					}
					if (!foundPoint) {
						newRelMode = false;
						System.out.println("did not find source");
					}
				}
			}
		});

		addMouseListener(new MouseAdapter() {

			/**
			 * mouseClicked is executed when the mouse is clicked on a component
			 */
			public void mouseClicked(MouseEvent e) {
				boolean clickedOnClass = false;
				for (ClassGraphicObject cgo : classForListeners) {
					if (e.getX() > cgo.getoX() && e.getY() > cgo.getoY()
							&& e.getX() < cgo.getoX() + cgo.getWidth()
							&& e.getY() < cgo.getoY() + cgo.getHeight()) {
						activeClassNotifier.changeClass(cgo.getModel());
						classForListeners.remove(cgo);
						classForListeners.add(cgo);
						repaint();
						invalidate();
						clickedOnClass = true;
						break;
					}
				}
				if (!clickedOnClass) {
					PackageGraphicObject pgo;
					for (int i = packagesForListeners.size() - 1; i >= 0; i--) {
						pgo = packagesForListeners.elementAt(i);
						if (e.getX() > pgo.getoX() && e.getY() > pgo.getoY()
								&& e.getX() < pgo.getoX() + pgo.getWidth()
								&& e.getY() < pgo.getoY() + pgo.getHeight()) {
							pgo.activate();
							activeClassNotifier.changePackage(pgo.getModel());
							packagesForListeners.remove(pgo);
							packagesForListeners.add(pgo);
							repaint();
							invalidate();
							break;
						}
					}
				}
			}
		});

		addMouseListener(new MouseAdapter() {

			/**
			 * mouseReleased is executed when the mouse is released on a
			 * component
			 */
			public void mouseReleased(MouseEvent e) {
				if (!newRelMode) {
					if (relationshipIsActive) {
						for (ClassGraphicObject cgo : classForListeners) {
							for (AnchorPoint ap : cgo.getModel()
									.getAnchorPoints()) {
								if ((((e.getX() - ap.x) * (e.getX() - ap.x)) + ((e
										.getY() - ap.y) * (e.getY() - ap.y))) <= 100) {
									if (srcOrDest == 0) {
										theActiveRelationship.setDest(ap);
									} else if (srcOrDest == 1) {
										theActiveRelationship.setSrc(ap);
									}
									activeClassNotifier
											.deactivateRelationship();
									break;
								}
							}
							if (!relationshipIsActive)
								break;
						}
					} else {
						for (ClassGraphicObject cgo : classForListeners) {

							cgo.deactivate();

						}
						for (PackageGraphicObject pgo : packagesForListeners) {
							pgo.deactivate();
						}
					}
					repaint();
					activeClassNotifier.deactivateRelationship();
				} else {
					boolean foundPoint = false;
					for (ClassGraphicObject cgo : classForListeners) {
						for (AnchorPoint ap : cgo.getAnchorPoints()) {
							if ((((e.getX() - ap.x) * (e.getX() - ap.x)) + ((e
									.getY() - ap.y) * (e.getY() - ap.y))) <= 100) {
								relFactory.setDest(ap);
								RelationshipImpl newRel = relFactory
										.buildRelationship();
								theController.addRelationship(newRel);
								foundPoint = true;
								repaint();
								invalidate();
								break;
							}

						}
						if (foundPoint)
							break;
					}
					repaint();
					invalidate();
					newRelMode = false;
				}

			}
		});

		addMouseMotionListener(new MouseAdapter() {

			/**
			 * mouseDragged executed when the mouse position changes while a
			 * mouse button is pressed
			 */
			public void mouseDragged(MouseEvent e) {
				for (ClassGraphicObject cgo : classForListeners) {
					if (cgo.isActive()) {
						moveClass(e.getX(), e.getY(), cgo);
					}
				}
				for (PackageGraphicObject pgo : packagesForListeners) {
					if (pgo.isActive()) {
						movePackage(e.getX(), e.getY(), pgo);
					}
				}
			}
		});
	}

	/**
	 * moveClass is used to move a class object on the panel
	 * 
	 * @param x
	 *            origin of x position
	 * @param y
	 *            origin of y position
	 * @param classToMove
	 *            the class object that is to be moved
	 */
	private void moveClass(int x, int y, ClassGraphicObject classToMove) {

		// Update coordinates.
		classToMove.setoX(x - mouseToOriginOffsetX);
		classToMove.setoY(y - mouseToOriginOffsetY);
		classToMove.getModel().updateAnchors();

		repaint();
		invalidate();

	}

	/**
	 * movePackage is used to move a package object on the panel
	 * 
	 * @param x
	 *            origin of x position
	 * @param y
	 *            origin of y position
	 * @param packageToMove
	 *            the package object that is to be moved
	 */
	private void movePackage(int x, int y, PackageGraphicObject packageToMove) {

		// Current square state, stored as final variables
		// to avoid repeat invocations of the same methods.
		final int CURR_X = packageToMove.getoX();
		final int CURR_Y = packageToMove.getoY();

		if ((CURR_X != x) || (CURR_Y != y)) {

			// Update coordinates.
			packageToMove.setoX(x);
			packageToMove.setoY(y);

			// Repaint the square at the new location.
			repaint();
			invalidate();
		}
	}

	/**
	 * getPreferredSize gets the dimensions for the panel
	 */
	public Dimension getPreferredSize() {
		return new Dimension(1024, 768);
	}

	/**
	 * redraw is used to redraw the panel when needed
	 */
	public void redraw() {
		repaint();
		invalidate();
	}

	/**
	 * paintComponent passes in the graphics needed to create the various
	 * objects on the panel
	 */
	public void paintComponent(Graphics g) {
		super.paintComponent(g);
		for (PackageGraphicObject pgo : packageToDisplay) {
			pgo.drawPackage(g);
		}
		for (ClassGraphicObject cgo : classToDisplay) {

			cgo.drawClass(g, relationshipIsActive || newRelMode);
		}

		for (RelationshipGraphicObject rgo : relationshipToDisplay) {
			rgo.drawRelationship(g);
		}

	}

	/**
	 * setNewRelMode sets the relationship mode
	 */
	public void setNewRelMode() {
		newRelMode = true;
	}

	/**
	 * unSetNewRelMode unsets the relationship mode
	 */
	public void unSetNewRelMode() {
		newRelMode = false;
	}

	/**
	 * actionPerformed executes when an action occurs
	 */
	@Override
	public void actionPerformed(ActionEvent arg0) {
		// TODO Auto-generated method stub

	}

	/**
	 * update simply updates the initialized fields
	 */
	@Override
	public void update(Observable obj, Object arg) {
		if (arg instanceof Boolean) {
			this.relationshipIsActive = (Boolean) arg;
		}
	}
}